package vip

import (
	"fmt"
	"time"
	"vdrift/internal/logger"

	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
)

type Manager struct {
	ifaceName string
	vipAddr   *Addr
	link      netlink.Link
}

func NewManager(ifaceName string, vipStr string) (*Manager, error) {
	vipAddr, err := ParseAddr(vipStr)
	if err != nil {
		return nil, fmt.Errorf("invalid vip: %w", err)
	}

	link, err := netlink.LinkByName(ifaceName)
	if err != nil {
		return nil, fmt.Errorf("interface %s not found: %w", ifaceName, err)
	}

	return &Manager{
		ifaceName: ifaceName,
		vipAddr:   vipAddr,
		link:      link,
	}, nil
}

// Bind VIP to interface
func (m *Manager) Bind() error {
	addr := &netlink.Addr{
		IPNet: m.vipAddr.IPNet,
		Label: m.vipAddr.Label,
	}

	if err := netlink.AddrAdd(m.link, addr); err != nil {
		if errno, ok := err.(unix.Errno); ok && errno == unix.EEXIST {
			logger.WithVIP(m.vipAddr.IPNet.String(), m.ifaceName).Info("VIP already bound")
			return nil
		}
		return fmt.Errorf("failed to bind VIP: %w", err)
	}

	logger.WithVIP(m.vipAddr.IPNet.String(), m.ifaceName).Info("VIP bound successfully")
	return nil
}

// Unbind VIP from interface
func (m *Manager) Unbind() error {
	addr := &netlink.Addr{
		IPNet: m.vipAddr.IPNet,
		Label: m.vipAddr.Label,
	}

	if err := netlink.AddrDel(m.link, addr); err != nil {
		if errno, ok := err.(unix.Errno); ok && errno == unix.EADDRNOTAVAIL {
			logger.WithVIP(m.vipAddr.IPNet.String(), m.ifaceName).Info("VIP already unbound")
			return nil
		}
		return fmt.Errorf("failed to unbind VIP: %w", err)
	}

	logger.WithVIP(m.vipAddr.IPNet.String(), m.ifaceName).Info("VIP unbound")
	return nil
}

// Check if VIP is currently bound on this interface
func (m *Manager) IsBound() (bool, error) {
	addrs, err := netlink.AddrList(m.link, netlink.FAMILY_ALL)
	if err != nil {
		return false, err
	}

	targetIP := m.vipAddr.IPNet.IP
	for _, addr := range addrs {
		if addr.IPNet.IP.Equal(targetIP) {
			// Optional: check label
			if m.vipAddr.Label == "" || addr.Label == m.vipAddr.Label {
				return true, nil
			}
		}
	}
	return false, nil
}

// Wait for interface to be up
func (m *Manager) WaitForInterface(timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		link, err := netlink.LinkByName(m.ifaceName)
		if err != nil {
			time.Sleep(1 * time.Second)
			continue
		}
		if link.Attrs().OperState == netlink.OperUp {
			m.link = link
			return nil
		}
		time.Sleep(1 * time.Second)
	}
	return fmt.Errorf("interface %s not up after %s", m.ifaceName, timeout)
}
